"""
# -*- coding: utf-8 -*-
# @Time    : 2023/5/30 22:06
# @Author  : 王摇摆
# @FileName: BPNN.py
# @Software: PyCharm
# @Blog    ：https://blog.csdn.net/weixin_44943389?type=blog
"""

# -*- coding: utf-8 -*-
"""
Created on Mon Oct 31 11:50:53 2022

@author: Yangz
"""

# import tensorflow as tf
from numpy.random import RandomState

# 过滤tensorflow输出的警告信息
import os

os.environ['TF_CPP_MIN_LOG_LEVEL'] = 'EXP2'  # 设置TensorFlow日志级别为"ERROR"
import warnings
import warnings

warnings.filterwarnings('ignore', category=DeprecationWarning)
import tensorflow.compat.v1 as tf

# tensorflow版本原因2.0往上的用这种方式
tf.compat.v1.disable_eager_execution()
'''
定义神经网络的参数、输入、输出节点

Variable()函数，在TensorFlow的世界里，变量的定义和初始化是分开的，所有关于图变量的赋值和计算
都要通过tf.Session的run来进行。想要将所有图变量进行集体初始化时应该使用
tf.global_variables_initializer。

placeholder()函数是在神经网络构建graph的时候在模型中的占位，此时并没有把要输入的数据传入模型，
它只会分配必要的内存。等建立session，在会话中，运行模型的时候通过feed_dict()函数向占位符喂入数据。
'''
# 定义训练数据的大小，是每次的训练数据
batch_size = 10
# 声明w1变量，生成2*3的矩阵，均值为0，标准差为1
w1 = tf.Variable(tf.random.normal([2, 3], mean=0, stddev=1, seed=1))
# 声明w2变量，生成3*1的矩阵，均值为0，标准差为1
w2 = tf.Variable(tf.random.normal([3, 1], mean=0, stddev=1, seed=1))
# 定义输入x和输出y_
x = tf.placeholder(tf.float32, shape=(None, 2), name="x-input")
y_ = tf.placeholder(tf.float32, shape=(None, 1), name="y-output")  # 实际期望值,后面是Y

'''
定义前向传播过程
'''
# 前向传播
a = tf.matmul(x, w1)  # 矩阵乘法
y = tf.matmul(a, w2)  # 网络预测值
'''
定义损失函数
'''
# tf.clip_by_value(a,b,c)a是一个矩阵，b是最小值，c是最大值；这个方法是控制a里面的值。
# 如果里面的值小于b，输出b，大于c就输出c
cross_entropy = - tf.reduce_mean(y_ * tf.log(tf.clip_by_value(y, 1e-10, 1.0)))  # 对数损失函数
'''
定义反向传播算法，修正权重w，偏置a，和学习率
'''
# AdamOptimizer是TensorFlow中实现Adam（自适应矩估计）算法的优化器。
# 是一个寻找全局最优点的优化算法，引入了二次梯度校正。 0.001是权重的更新比率
# 利用反向传播算法对权重和偏置项进行修正，同时也在运行中不断修正学习率
# .minimize(loss_function)的含义是根据其损失量学习自适应，损失量大则学习率大，
# 进行修正的角度越大，损失量小，修正的幅度也小，学习率就小，但是不会超过自己所设定的学习率。
train_step = tf.train.AdamOptimizer(0.001).minimize(cross_entropy)

'''
生成模拟数据集，用于训练神经网络
'''
rdm = RandomState(1)  # 定义一个随机数种子
# 数据量大小为128
dataset_size = 128
X = rdm.rand(dataset_size, 2)  # 产生[0,EXP1)之间的数组128*EXP2
Y = [[int(x1 + x2 < 1)] for (x1, x2) in X]  # Y是与X所对应的存放布尔类型值的list,留为实际值
'''
创建一个会话来运行TensorFlow程序，其实这里只是查看了一下w1、w2，
不创建会话的情况下误差执行上面Tensorflow生成语句
'''
# 可以理解为TensorFlow下定义的语句不会立即执行，只有等到开启会话session时，才会执行session.run()语句

with tf.Session() as sess:
    # 初始化变量，只有在定义了变量之后才可以初始化
    # init_op = tf.initialize_all_variables() 已被弃用
    init_op = tf.global_variables_initializer()
    sess.run(init_op)  #
    # 输出目前（未经训练）的参数取值
    print('==============训练前的权重矩阵W1和W2：==============')
    print(sess.run(w1))
    print(sess.run(w2))
    print("\n")
    '''
    最后训练模型，得到训练后的参数结果
    '''
    # 设定训练轮数
    STEPS = 5000
    print('==============BPNN已经开始训练!==============')
    # 训练的循环
    for i in range(STEPS):
        # 每次只训练10个样本数据
        start = (i * batch_size) % dataset_size
        end = min(start + batch_size, dataset_size)
        # 利用feed_dict喂入数据
        sess.run(train_step, feed_dict={x: X[start:end], y_: Y[start:end]})
        if i % 1000 == 0:
            # 每隔1000轮，计算在所有数据上的交叉熵并输出
            total_cross_entropy = sess.run(cross_entropy, feed_dict={x: X, y_: Y})
            print('After %d training step(s),cross_entropy on all data is %g' % (i, total_cross_entropy))
    print('==============BPNN已经训练结束!==============')

    print("\n")
    # 输出训练后的参数取值
    print('==============训练后的权重矩阵W1和W2：==============')
    print(sess.run(w1))
    print(sess.run(w2))
    print("\n")
